/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <type_traits>

#include "dal/allocator.h"
#include "dal/topic.h"
#include "dal_fastdds/utils/ReferenceCount.hpp"
#include "dal_fastdds/converter/StatusConverter.hpp"
#include "dal_fastdds/converter/StatusMaskConverter.hpp"
#include "dal_fastdds/converter/QosConverter.hpp"
#include "dal_fastdds/converter/RetTypeConverter.hpp"
#include "dal_fastdds/listener_impl/TopicListenerImpl.hpp"
#include "dal_fastdds/utils/log.hpp"
#include "dal_fastdds/utils/EntityGetter.hpp"
#include "dal_fastdds/utils/EntityMappingManager.hpp"
#include "dal_fastdds/config_manager/ConfigManager.hpp"

#include "dal_utils/StrHelper.hpp"
#include "dal_utils/MsgLibLoader.hpp"

#include "dal_msg/dalidl_runtime_c/message_type_support_struct.h"
#include "dalidl_typesupport_fastrtps_cpp/identifier.hpp"
#include "dal_fastrtps_cpp/type_support_common.hpp"
#include "dal_msg/dalidl_runtime_cpp/traits.hpp"
#include "dalidl_typesupport_cpp/message_type_support.hpp"
#include "dalidl_typesupport_fastrtps_cpp/message_type_support.h"

#include <fastdds/dds/topic/TypeSupport.hpp>

using namespace eprosima::fastdds::dds;

dal_type_support_t* dal_create_type_support(void* type_support)
{
    if (!type_support) {
        LOG_ERROR() << "TypeSupport is null. " << LOG_ENDL;
        return nullptr;
    }

    // fully qualified name
    const char* fqname = static_cast<const char*>(type_support);
    if (!MsgLibLoader::loadLibrary(fqname)) {
        LOG_ERROR() << "Load msg lib failed." << LOG_ENDL;
        return nullptr;
    }

    void* func = MsgLibLoader::getFunction(fqname);
    if (!func) {
        LOG_ERROR() << "Get function failed." << LOG_ENDL;
        return nullptr;
    }

    const dalidl_message_type_support_t* ts_handles =
        reinterpret_cast<const dalidl_message_type_support_t* (*)()>(func)();
    if (!ts_handles) {
        LOG_ERROR() << "Get message type supports failed." << LOG_ENDL;
        return nullptr;
    }

    const dalidl_message_type_support_t* ts_handle =
        get_message_typesupport_handle(ts_handles, DAL_FASTRTPS_CPP_TYPESUPPORT_CPP);
    if (!ts_handle) {
        LOG_ERROR() << "Get message type support handle failed." << LOG_ENDL;
        return nullptr;
    }

    auto callbacks = static_cast<const message_type_support_callbacks_t*>(ts_handle->data);
    std::string type_name = _create_type_name(callbacks);
    auto tsupport = new (std::nothrow) MessageTypeSupport_cpp(callbacks);

    auto ts = dal_type_support_allocate();
    ts->handle_ = new TypeSupport(tsupport);
    ts->type_name_ = dal_strdup(type_name.c_str());
    ts->type_support_impl_ = callbacks;

    MsgLibLoader::typeSupportMap_[type_name] = callbacks;

    return ts;
}

dal_ret_t dal_delete_type_support(dal_type_support_t* ts)
{
    if (ts == nullptr || ts->handle_ == nullptr)
        return DAL_RETCODE_BAD_PARAMETER;

    delete reinterpret_cast<TypeSupport*>(ts->handle_);
    dal_strfree(ts->type_name_);
    dal_type_support_free(ts);
    reinterpret_cast<dal_type_support_t*>(ts)->handle_ = nullptr;
    return DAL_RETCODE_OK;
}

const char* dal_type_support_get_type_name(dal_type_support_t* ts)
{
    if (ts == nullptr) {
        LOG_ERROR() << "TypeSupport is null." << LOG_ENDL;
        return nullptr;
    }
    return ts->type_name_;
}

dal_ret_t dal_type_support_register_type(dal_type_support_t* ts,
                                         dal_domain_participant_t* participant,
                                         const char* type_name)
{
    if (ts == nullptr) {
        LOG_ERROR() << "TypeSupport is null." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    if (type_name == nullptr || strlen(type_name) == 0)
        return DAL_RETCODE_BAD_PARAMETER;

    DomainParticipant* f_participant = get_fastdds_domain_participant(participant);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "get fastdds domain participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    auto type_support = reinterpret_cast<TypeSupport*>(ts->handle_);
    auto f_ret = type_support->register_type(f_participant);
    return RetTypeConverter::GetInstance()->ConvertRetFromFastddsToDal(f_ret);
}

dal_topic_listener_t* dal_create_topic_listener(dal_topic_listener_callbacks_t callbacks)
{
    auto listener = dal_topic_listener_allocate();
    if (listener == nullptr) {
        return nullptr;
    }
    listener->handle_ = new TopicListenerImpl(callbacks);
    if (!listener->handle_)
        LOG_ERROR() << "TopicListenerImpl memory allocation failed." << LOG_ENDL;
    return listener;
}

dal_ret_t dal_delete_topic_listener(dal_topic_listener_t* listener)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    delete reinterpret_cast<TopicListenerImpl*>(listener->handle_);
    dal_topic_listener_free(listener);
    reinterpret_cast<dal_topic_listener_t*>(listener)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_topic_t* dal_domain_participant_create_topic(dal_domain_participant_t* participant,
                                                 const char* topic_name,
                                                 const char* type_name,
                                                 dal_topic_qos_t* qos,
                                                 dal_topic_listener_t* listener,
                                                 dal_status_mask_t mask)
{
    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (!topic_name || !type_name || strlen(topic_name) == 0 || strlen(type_name) == 0) {
        LOG_ERROR_WITH_LOCATION() << "topic or type name is invalid parameter." << LOG_ENDL;
        return nullptr;
    }

    DomainParticipant* f_participant = get_fastdds_domain_participant(participant);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "get fastdds domain participant failed." << LOG_ENDL;
        return nullptr;
    }

    auto d_topic = dal_topic_allocate();
    if (!d_topic) {
        LOG_ERROR_WITH_LOCATION() << "dal topic allocate failed." << LOG_ENDL;
        return nullptr;
    }

    auto ts_impl = MsgLibLoader::typeSupportMap_[type_name];
    if (!ts_impl) {
        LOG_ERROR_WITH_LOCATION() << "type support not created." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }
    d_topic->type_support_impl_ = ts_impl;

    TopicQos f_qos;
    d_ret = QosConverter::GetInstance()->ConvertTopicQosFromDalToFastdds(qos, &f_qos);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert topic qos failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    StatusMask f_mask;
    d_ret = StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToFastdds(mask, &f_mask);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert qos statusmask failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    auto f_listener = listener ? reinterpret_cast<TopicListenerImpl*>(listener->handle_) : nullptr;
    auto f_topic = f_participant->create_topic(topic_name, type_name, f_qos, f_listener, f_mask);
    if (!f_topic) {
        LOG_ERROR_WITH_LOCATION() << "create topic failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    EntityMappingAddTopic(f_topic, d_topic);

    DalTopicImpl* handle_ = new DalTopicImpl(f_topic, f_listener);
    handle_->SetFastddsDomainParticipant(f_participant);
    d_topic->handle_ = handle_;
    return d_topic;
}

dal_topic_t* dal_domain_participant_create_topic_with_profile(dal_domain_participant_t* participant,
                                                              const char* topic_name,
                                                              const char* type_name,
                                                              dal_topic_qos_t* qos,
                                                              const char* profile_name,
                                                              dal_topic_listener_t* listener,
                                                              dal_status_mask_t mask)
{
    if (!profile_name || strlen(profile_name) == 0) {
        return dal_domain_participant_create_topic(
            participant, topic_name, type_name, qos, listener, mask);
    }

    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (!topic_name || !type_name || strlen(topic_name) == 0 || strlen(type_name) == 0) {
        LOG_ERROR_WITH_LOCATION() << "topic or type name is invalid parameter." << LOG_ENDL;
        return nullptr;
    }

    DomainParticipant* f_participant = get_fastdds_domain_participant(participant);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "get fastdds domain participant failed." << LOG_ENDL;
        return nullptr;
    }

    auto d_topic = dal_topic_allocate();
    if (!d_topic) {
        LOG_ERROR_WITH_LOCATION() << "dal topic allocate failed." << LOG_ENDL;
        return nullptr;
    }

    auto ts_impl = MsgLibLoader::typeSupportMap_[type_name];
    if (!ts_impl) {
        LOG_ERROR_WITH_LOCATION() << "type support not created." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }
    d_topic->type_support_impl_ = ts_impl;

    TopicQos f_qos;
    if (!ConfigManager::GetInstance()->getFastddsProfilePath().empty()) {
        try {
            f_participant->get_topic_qos_from_profile(profile_name, f_qos);
        }
        catch (...) {
            LOG_ERROR_WITH_LOCATION()
                << "topic qos profile[" << profile_name << "] in xml are not effective.";
            dal_topic_free(d_topic);
            return nullptr;
        }
    } else {
        d_ret = QosConverter::GetInstance()->ConvertTopicQosFromDalToFastdds(qos, &f_qos);
        if (DAL_RETCODE_OK != d_ret) {
            LOG_ERROR_WITH_LOCATION() << "convert topic qos failed." << LOG_ENDL;
            dal_topic_free(d_topic);
            return nullptr;
        }
    }

    StatusMask f_mask;
    d_ret = StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToFastdds(mask, &f_mask);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert qos statusmask failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    auto f_listener = listener ? reinterpret_cast<TopicListenerImpl*>(listener->handle_) : nullptr;
    auto f_topic = f_participant->create_topic(topic_name, type_name, f_qos, f_listener, f_mask);
    if (!f_topic) {
        LOG_ERROR_WITH_LOCATION() << "create topic failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    EntityMappingAddTopic(f_topic, d_topic);

    DalTopicImpl* handle_ = new DalTopicImpl(f_topic, f_listener);
    handle_->SetFastddsDomainParticipant(f_participant);
    d_topic->handle_ = handle_;
    return d_topic;
}

dal_ret_t dal_domain_participant_delete_topic(dal_domain_participant_t* participant,
                                              dal_topic_t* topic)
{
    DomainParticipant* f_participant = get_fastdds_domain_participant(participant);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "get fastdds domain participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    Topic* f_topic = get_fastdds_topic(topic);
    if (!f_topic) {
        LOG_ERROR_WITH_LOCATION() << "get fastdds topic failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    if (!f_participant || !f_topic) {
        LOG_ERROR_WITH_LOCATION() << "fastdds topic or participant is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    auto f_ret = f_participant->delete_topic(f_topic);
    auto d_ret = RetTypeConverter::GetInstance()->ConvertRetFromFastddsToDal(f_ret);
    if (DAL_RETCODE_OK != d_ret) {
        dal_topic_free(topic);
        LOG_ERROR_WITH_LOCATION() << "delete topic failed." << d_ret << LOG_ENDL;
        return d_ret;
    }

    EntityMappingRemoveTopic(f_topic);

    auto topic_impl = reinterpret_cast<DalTopicImpl*>(topic->handle_);
    delete topic_impl;
    dal_topic_free(topic);
    reinterpret_cast<dal_topic_t*>(topic)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}